/*******************************************************************************
 * Copyright 2010 Simon Mieth
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package cn.kkdlk.parse;

import cn.kkdlk.parse.common.*;
import cn.kkdlk.parse.entities.Entity;
import cn.kkdlk.parse.entities.Viewport;
import cn.kkdlk.parse.entities.util.DimensionStyle;
import cn.kkdlk.parse.entities.util.HatchPattern;
import cn.kkdlk.parse.entities.util.Utils;
import cn.kkdlk.parse.math.Bounds;
import cn.kkdlk.parse.objects.Dictionary;
import cn.kkdlk.parse.objects.DraftObject;
import cn.kkdlk.parse.util.Constants;

import java.util.*;

/**
 * @author <a href="mailto:simon.mieth@gmx.de>Simon Mieth</a>
 */
public class DraftDocument {

    public static final String PROPERTY_ENCODING = "encoding";

    public static final double DEFAULT_MARGIN = 5;

    private final Map<String, Layer> layers = new TreeMap<String, Layer>();

    private final Map<String, Block> blocks = new TreeMap<String, Block>();

    private final Map<String, LineType> lineTypes = new TreeMap<String, LineType>();

    private final Map<String, DimensionStyle> dimensionStyles = new HashMap<String, DimensionStyle>();

    private final Map<String, Style> textStyles = new HashMap<String, Style>();

    // the user coordinate systems (not used?)
    //private Hashtable ucs = new Hashtable();

    private final Map<String, String> properties = new HashMap<String, String>();

    private final List<Viewport> viewports = new ArrayList<Viewport>();

    private Bounds bounds = new Bounds();

    private Header header = new Header();

    private final HashMap<String, Map<Long, DraftObject>> objects = new HashMap<String, Map<Long, DraftObject>>();

    private final HashMap<String, HatchPattern> patterns = new HashMap<String, HatchPattern>();

    private final List<View> views = new ArrayList<View>();

    private cn.kkdlk.parse.objects.Dictionary rootDictionary = new cn.kkdlk.parse.objects.Dictionary();

    private final Layer defaultLayer = new Layer();

    public DraftDocument() {
        // the defalut layer

        defaultLayer.setDocument(this);
        defaultLayer.setName(Constants.DEFAULT_LAYER);
        defaultLayer.setID(Utils.generateNewID(this));
        this.layers.put(Constants.DEFAULT_LAYER, defaultLayer);

        // setup the root Dictionary
        this.rootDictionary = new cn.kkdlk.parse.objects.Dictionary();
        this.rootDictionary.setDocument(this);
    }

    public void addLayer(Layer layer) {
        layer.setDocument(this);
        this.layers.put(layer.getName(), layer);
        layer.setZIndex(this.layers.size());

    }

    /**
     * Returns the specified layer.
     *
     * @param key The layer id
     * @return the layer or if not found the default layer (layer "0")
     */
    public Layer getLayer(String key) {
        if (this.layers.containsKey(key)) {
            return layers.get(key);
        }

        // retrun the default layer
        if (this.layers.containsKey(Constants.DEFAULT_LAYER)) {
            return layers.get(Constants.DEFAULT_LAYER);
        } else {
            Layer layer = new Layer();
            layer.setName(Constants.DEFAULT_LAYER);
            this.addLayer(layer);
            return layer;
        }
    }

    /**
     * Returns true if the document contains the specified layer.
     *
     * @param layerName the layer name
     * @return true - if the document contains the layer, otherwise false
     */
    public boolean containsLayer(String layerName) {
        return this.layers.containsKey(layerName);
    }

    public Collection<Layer> getLayers() {
        return layers.values();
    }

    /**
     * Adds a line type to this document
     *
     * @param ltype - the line type to add
     */

    public void addLineType(LineType ltype) {
        this.lineTypes.put(ltype.getName(), ltype);
    }

    /**
     * Gets the line type by name
     *
     * @param name of the {@see org.kabeja.common.LineType}
     * @return
     */
    public LineType getLineType(String name) {
        return this.lineTypes.get(name);
    }

    /**
     * Get the {@see java.util.Collection} of all containing {@see org.kabeja.common.LineType}
     *
     * @return the {@see java.util.Collection} of {@see org.kabeja.common.LineType}
     */

    public Collection<LineType> getLineTypes() {
        return this.lineTypes.values();
    }


    public boolean containsLineType(String name) {
        return this.lineTypes.containsKey(name);
    }

    /**
     * Adds the given to the DraftDocument. It will placed on the layer of the
     * entity if this one exists otherwise the entity will be added to the
     * RootLayer "0".
     *
     * @param entity {@see org.kabeja.common.DraftEntity}
     */

    public void addEntity(DraftEntity entity) {
        Layer layer = entity.getLayer();
        if (layer == null) {
            layer = this.getRootLayer();
        }
        layer.addEntity(entity);
        layer.setDocument(this);
    }

    /**
     * Add a block to the DraftDocument, all entities of the block get this
     * DraftDocument as OwnerDocument
     *
     * @param block
     */

    public void addBlock(Block block) {
        block.setDocument(this);
        this.blocks.put(block.getName(), block);
    }

    /**
     * Get a block by the given name.
     *
     * @param name
     * @return The Block with the given by {@link Block#getName() or null if there is no such block
     */

    public Block getBlock(String name) {
        return blocks.get(name);
    }

    /**
     * Get the collection of all Blocks of the DraftDocument
     *
     * @return the collection of all Blocks
     */
    public Collection<Block> getBlocks() {
        return blocks.values();
    }


    /**
     * set a property of the DraftDocument
     *
     * @param key
     * @param value
     */


    public void setProperty(String key, String value) {
        this.properties.put(key, value);
    }

    /**
     * Get a property of the given name
     *
     * @param key
     * @return the property value or null if there is no such property
     */

    public String getProperty(String key) {
        if (properties.containsKey(key)) {
            return properties.get(key);
        }

        return null;
    }

    public boolean hasProperty(String key) {
        return this.properties.containsKey(key);
    }

    /**
     * Returns the {@see org.kabeja.math.Bounds} of this document
     *
     * @return
     */
    public Bounds getBounds() {
        this.bounds = new Bounds();

        for (Layer layer : this.layers.values()) {
            if (!layer.isFrozen()) {
                Bounds b = layer.getBounds();

                if (b.isValid()) {
                    this.bounds.addToBounds(b);
                }
            }
        }
        return bounds;
    }

    /**
     * Returns the bounds of this document by
     * distinguished model space or paper space
     *
     * @return
     */
    public Bounds getBounds(boolean onModelspace) {
        Bounds bounds = new Bounds();

        for (Layer layer : this.layers.values()) {
            if (!layer.isFrozen()) {
                Bounds b = layer.getBounds(onModelspace);

                if (b.isValid()) {
                    bounds.addToBounds(b);
                }
            }
        }

        return bounds;
    }


    public Header getHeader() {
        return this.header;
    }

    public void setHeader(Header header) {
        this.header = header;
    }

    public void addDimensionStyle(DimensionStyle style) {
        this.dimensionStyles.put(style.getName(), style);
    }

    public DimensionStyle getDimensionStyle(String name) {
        return this.dimensionStyles.get(name);
    }

    public Collection<DimensionStyle> getDimensionStyles() {
        return this.dimensionStyles.values();
    }

    public void addStyle(Style style) {
        this.textStyles.put(style.getName(), style);
    }

    public Style getStyle(String name) {
        return this.textStyles.get(name);
    }

    public Collection<Style> getStyles() {
        return this.textStyles.values();
    }

    public void removeLayer(String id) {
        this.layers.remove(id);
    }

    public void addViewport(Viewport viewport) {
        this.viewports.add(viewport);
    }

    public Collection<Viewport> getViewports() {
        return this.viewports;
    }

    public void removeViewport(Viewport viewport) {
        this.viewports.remove(viewport);
    }

    public void removeViewport(int index) {
        this.viewports.remove(index);
    }

    public void addView(View view) {
        this.views.add(view);
    }

    public Collection<View> getViews() {
        return this.views;
    }

    public void addObject(DraftObject obj) {
        // look if the object goes in a dictionary
        cn.kkdlk.parse.objects.Dictionary d = this.rootDictionary.getDictionaryForID(obj.getID());

        if (d != null) {
            d.putObject(obj);
        } else {
            // is not bound to a dictionary
            Map<Long, DraftObject> type = null;

            if (this.objects.containsKey(obj.getObjectType())) {
                type = objects.get(obj.getObjectType());
            } else {
                type = new HashMap<Long, DraftObject>();
                this.objects.put(obj.getObjectType(), type);
            }

            type.put(obj.getID(), obj);
        }
    }

    /**
     * Returns the root dictionary.
     *
     * @return the root Dictionray
     */
    public cn.kkdlk.parse.objects.Dictionary getRootDictionary() {
        return this.rootDictionary;
    }

    public void setRootDictionary(Dictionary root) {
        this.rootDictionary = root;
    }

    public Layer getRootLayer() {
        return this.defaultLayer;
    }

    public List<DraftObject> getObjectsByType(String type) {
        Map<Long, DraftObject> objecttypes = this.objects.get(type);
        List<DraftObject> list = new ArrayList<DraftObject>(objecttypes.values());

        return list;
    }

    /**
     * @param id , the ID of the object
     * @return the object
     */
    public DraftObject getObjectByID(long id) {
        for (Map<Long, DraftObject> map : this.objects.values()) {
            DraftObject obj;
            if ((obj = map.get(id)) != null) {
                return obj;
            }
        }

        // Nothing found --> search in the dictionaries
        return this.rootDictionary.getObjectByID(id);
    }

    /**
     * Gets the
     *
     * @param id of the
     * @return the
     * @see Entity with the specified ID.
     * @see Entity
     * @see Entity with the specified ID or null if there is no
     * @see Entity with the specified ID
     */
    public DraftEntity getEntityByID(long id) {
        DraftEntity entity = null;

        for (Layer layer : this.layers.values()) {

            if ((entity = layer.getEntityByID(id)) != null) {
                return entity;
            }
        }

        for (Block block : this.blocks.values()) {
            if ((entity = block.getEntityByID(id)) != null) {
                return entity;
            }
        }

        return entity;
    }

    /**
     * Adds a HatchPattern to the document.
     *
     * @param pattern
     */
    public void addHatchPattern(HatchPattern pattern) {
        this.patterns.put(pattern.getID(), pattern);
    }

    /**
     * Get all HatchPattern of the document
     *
     * @return {@see java.util.Collection} of all HatchPattern of this document
     */
    public Collection<HatchPattern> getHatchPatterns() {
        return this.patterns.values();
    }

    /**
     * @param ID of the pattern (also called pattern name)
     * @return the HatchPattern or null
     */
    public HatchPattern getHatchPattern(String id) {
        return this.patterns.get(id);
    }
}
